/* Xmms2 interface object. */

#include <limits.h>  // UINT_MAX

#include "mcp.h"
#include "client.h"

// IDInfo declaration (private)                                           >fold>

static const unsigned int NULL_ID = UINT_MAX;

class IDInfo
    {
public:
//    IDInfo (unsigned int set_id) : id(set_id), error(0), info(0) {}
//    ~IDInfo () { if ( info ) info->unref(); }
    unsigned int  id;
    char         *error;
    MediaInfo    *info;
    };

// ---------------------------------------------------------------------- <fold<
// MediaInfo definition                                                   >fold>

//static MediaInfo blankinfo(NULL_ID);

MediaInfo::MediaInfo (int set_id)
    : id(set_id), ref_count(0)
    {}

//MediaInfo::~MediaInfo () { }

MediaInfo * MediaInfo::addref ()
    {
    ref_count++;
    return this;
    }

void MediaInfo::unref ()
    {
    if ( --ref_count <= 0 )
        delete this;
    }

/*
void MediaInfo::clear_picture ()
    {
    free(picture_data);
    picture_data = 0;
    }

bool MediaInfo::blank ()
    { return id == NULL_ID; }
*/

// -----------------------------------------------------------------------<fold<
//      XMMS2 callbacks                                                   >fold>

void xmms_changed_id (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;
    unsigned int new_id;

    if ( xmmsc_result_iserror(result) )
        mcp->disconnected_event();
    else if ( !xmmsc_result_get_uint(result, &new_id) )
        mcp->disconnected_event();
    else
        {
        mcp->log_verbose(10, "xmms_changed_id: (%u) - %u\n",
                         obj->current_id, new_id);

        if ( obj->current_info ) obj->current_info->unref();
        obj->current_info = 0;
        obj->current_info = obj->find_loaded_mediainfo(new_id);
        if ( obj->current_info ) obj->current_info->addref();

        obj->current_id = new_id;
        mcp->id_event(obj->current_id);

        if ( obj->current_info )
            mcp->current_info_updated(obj->current_info);
        else
            obj->request(Client::REQUEST_CURRENT_INFO);
        }
    }

void xmms_got_id (xmmsc_result_t *result, void *userdata)
    {
    xmms_changed_id(result, userdata);
    xmmsc_result_unref(result);
    }

void xmms_changed_pos (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;

    unsigned int index;
    if ( xmmsc_result_iserror(result) )
        {
        mcp->log_verbose(10, "xmms_changed_pos: %s\n",
                         xmmsc_result_get_error(result));
        obj->current_index = NULL_ID;
        }
//    else if ( !xmmsc_result_get_uint(result, &obj->current_index) )
    else if ( !xmmsc_result_get_dict_entry_uint(result, "position", &index) )
        {
        mcp->log_verbose(10, "xmms_changed_pos: no position result?\n");
        obj->current_index = NULL_ID;
        }
    else
        {
        // spurious position changes seem to get sent during a playlist
        // change, one for each added entry
        if ( index == obj->current_index )
            return;
        mcp->log_verbose(10, "xmms_changed_pos: %u\n", index);
        obj->current_index = index;
        mcp->position_event((int)index);
        }
    if ( obj->current_index == NULL_ID )
        mcp->position_event(-1);
    }

void xmms_got_pos (xmmsc_result_t *result, void *userdata)
    {
    xmms_changed_pos(result, userdata);
    xmmsc_result_unref(result);
    }

void xmms_changed_status (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;

    mcp->log_verbose(12, "xmms_changed_status\n");
    if ( xmmsc_result_iserror(result) )
        mcp->disconnected_event();
    else if ( !xmmsc_result_get_uint(result, &obj->current_status) )
        mcp->disconnected_event();
    else
        {
        mcp->log_verbose(12, "  (now %u)\n", obj->current_status);
        if ( obj->current_status == 0 )
            {
            mcp->stopped_event();
            // FIXME  temporary? until xmms2 sends this..
            if ( obj->maintain_playtime )
                mcp->playtime_event(0);
            }
        else if ( obj->current_status == 1 )
            {
            mcp->playing_event();
            }
        else if ( obj->current_status == 2 )
            {
            if ( obj->pending_seek )
                {
                result = xmmsc_playback_seek_ms(obj->xmms, obj->pending_seek);
                obj->pending_seek = 0;
                }
            mcp->paused_event();
            // kludge to handle the client-loaded-when-paused case
            obj->request(Client::REQUEST_PLAYTIME);
            }
        }
    }

// FIXME possibly delete this and always use the changed_status callback
//      only purpose is to not free a broadcast result  ... what did this mean?
void xmms_got_status (xmmsc_result_t *result, void *userdata)
    {
//fprintf(stderr, "xmms_got_status");
    xmms_changed_status(result, userdata);
    xmmsc_result_unref(result);
    }

void xmms_playtime_single (xmmsc_result_t *result, void *userdata)
    {
    unsigned int playtime;

    if ( !xmmsc_result_iserror(result) &&
         xmmsc_result_get_uint(result, &playtime) )
        {
        mcp->playtime_event(playtime);
        }
    xmmsc_result_unref(result);
    }

void xmms_playtime (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;
    unsigned int playtime;

    if ( xmmsc_result_iserror(result) )
        mcp->disconnected_event();
    else if ( !xmmsc_result_get_uint(result, &playtime) )
        mcp->disconnected_event();
    else
        {
        mcp->log_verbose(12, "playtime: %u\n", playtime);
        mcp->playtime_event(playtime);
        if ( obj->maintain_playtime )
            {
            xmmsc_result_t *restart = xmmsc_result_restart(result);
            xmmsc_result_unref(restart);
            }
        // This should be in xmms_changed_status but it doesn't work when
        // called during the status change event. (up to DrLecter)
        if ( obj->pending_seek )
            {
            xmmsc_result_unref(result);
            mcp->log_verbose(11, "post play seek: %u\n", obj->pending_seek);
            result = xmmsc_playback_seek_ms(obj->xmms, obj->pending_seek);
            obj->pending_seek = 0;
            }
        }
    xmmsc_result_unref(result);
    }

void xmms_changed_playlist (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;
    mcp->log_verbose(11, "xmms_changed_playlist\n");
    if ( xmmsc_result_iserror(result) )
        mcp->disconnected_event();
    else
        obj->playlist_changed();
    }

/*
void xmms_output_needed (int output_needed, void *userdata)
    {
//fprintf(stderr, "xmms_output_needed\n");
    MCP *callback = (MCP *)userdata;
    callback->outputNeeded(output_needed);
    }
*/

void xmms_disconnected (void *userdata)
    {
    Client *obj = (Client *)userdata;
    obj->disconnect();
    }

void xmms_quit (xmmsc_result_t *result, void *userdata)
    {
    //Client *obj = (Client *)userdata;
    mcp->log_verbose(11, "xmms_quit\n");
    // Disconnect and quit are synonymous as far as this client is concerned.
    }

#if 0
void read_volume (const void *key, xmmsc_result_value_type_t type,
                  const void *value, void *userdata)
    {
    Client *x2m = (x2m::Client *)userdata;
    // _UINT32 _INT32 _STRING _DICT _PROPDICT _COLL _BIN
    if ( type == XMMSC_RESULT_VALUE_TYPE_UINT32 )
        {
        if ( !strcmp((const char *)key, "left") )
            x2m->left_volume = XPOINTER_TO_UINT(value);
        else if ( !strcmp((const char *)key, "right") )
            x2m->right_volume = XPOINTER_TO_UINT(value);
        else
            fprintf(stderr, "read_volume: key %s?\n", key);
        }
    }
#endif

void xmms_changed_volume (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;
    mcp->log_verbose(12, "xmms_changed_volume");

    if ( xmmsc_result_iserror(result) )
        mcp->disconnected_event();
    else
        {
        bool changed = false;
        unsigned int num;
        // FIXME obtain channel names
        if ( xmmsc_result_get_dict_entry_uint(result, "left", &num) )
            { obj->left_volume = num; changed = true; }
        if ( xmmsc_result_get_dict_entry_uint(result, "right", &num) )
            { obj->right_volume = num; changed = true; }

        if ( changed )
            {
            mcp->log_verbose(11, "Volume change: (%d, %d)\n",
                             obj->left_volume, obj->right_volume);
            if ( obj->left_volume > 0 || obj->right_volume > 0 )
                {
                obj->left_muted = 0;
                obj->right_muted = 0;
                }
            mcp->volume_event(obj->left_volume, obj->right_volume);
            }
#if 0
        int left = x2m->left_volume, right = x2m->right_volume;
        xmmsc_result_dict_foreach(result, read_volume, userdata);
        if ( left != x2m->left_volume || right != x2m->right_volume )
            mcp->changed_volume(x2m->left_volume, x2m->right_volume);
#endif
        }
    }

// ---------------------------------------------------------------------- <fold<
//     Playlist / Media Overview                                          >fold>

/* playlist: IDInfo
 * -  contains the id and a MediaInfo structure (possibly blank)
 *
 * playlist_changed()
 * -  set need_playlist, which enables the mainloop timer.
 *    every timer call, the playlist can then be requested.
 *
 * read_playlist()
 * -  hold on to the old playlist, and create a new one
 * -  copy mediainfo from the old to the new
 * -  fire the playlist_updated event
 *
 * read_Mediainfo()
 * -  add info retrieved to the playlist, or discard if no match
 * -  if the playlist now has all entries updated, fire the
 *    playlist_info event
 */
// ---------------------------------------------------------------------- <fold<
// Playlist / Mediainfo                                                   >fold>

MediaInfo * Client::find_loaded_mediainfo (unsigned int id)
    {
    if ( current_info && current_info->id == id )
        return current_info;
    for ( unsigned int i = 0; i < playlist_length; i++ )
        if ( playlist[i].info && playlist[i].info->id == id )
            return playlist[i].info;
    return 0;
    }

void Client::playlist_changed ()
    {
    need_playlist = true;
    }

void xmms_read_playlist (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;
    obj->read_playlist(result);
    xmmsc_result_unref(result);
    }

void Client::read_playlist (xmmsc_result_t *result)
    {
    IDInfo *old;
    unsigned int old_length;
    unsigned int alloc_length;

    bool logging = mcp->log_verbose(10, "new playlist received:\n");
    need_playlist = false;

    old = playlist;
    old_length = playlist_length;

    playlist = 0;
    playlist_length = 0;
    alloc_length = 0;
    mcp->logf(logging, "  [");
    while ( xmmsc_result_list_valid(result) )
        {
        unsigned int id;
        if ( !xmmsc_result_get_uint(result, &id) )
            {
            mcp->log_verbose(2, "\nrmc-xmms2: error reading playlist id\n");
            mcp->disconnected_event();
            return;
            }

        playlist_length++;
        if ( alloc_length < playlist_length )
            {
            alloc_length += 64;
            playlist = (IDInfo *)realloc(playlist, sizeof(IDInfo) * alloc_length);
            if ( !playlist )
                {
                mcp->log_verbose(1, "rmc-xmms2: %s\n", strerror(errno));
                mcp->disconnected_event();
                }
            }
        mcp->logf(logging, " %u", id);
        playlist[playlist_length - 1].id = id;
        playlist[playlist_length - 1].info = 0;
        playlist[playlist_length - 1].error = 0;

        xmmsc_result_list_next(result);
        }
    mcp->logf(logging, " ]\n");

    // Start by copying the old to the new playlist since it may
    // have been altered rather than wholly changed
    unsigned int start;
    for ( start = 0;
          start < playlist_length && start < old_length &&
              playlist[start].id == old[start].id;
          start++ )
        {
        if ( old[start].info )
            playlist[start].info = old[start].info->addref();
        }

    // FIXME  use some kind of algorithm here that rises above
    //        the level of a bubble sort
    //        -  Probably not worth the effort here
    // copy already-loaded info from the old playlist
    for ( unsigned int oi = start; oi < old_length; oi++ )
        if ( old[oi].info )
            {
            for ( unsigned int j = start; j < playlist_length; j++ )
                if ( !playlist[j].info && playlist[j].id == old[oi].id )
                    playlist[j].info = old[oi].info->addref();
            }

    for ( unsigned int oi = 0; oi < old_length; oi++ )
        {
        if ( old[oi].info ) old[oi].info->unref();
        if ( old[oi].error ) free(old[oi].error);
        }
    free(old);

    mcp->playlist_event();
    }

void xmms_current_info (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;
    if ( obj->current_info )
        {
        obj->current_info->unref();
        obj->current_info = 0;
        }
    obj->read_Mediainfo(result, false);
    xmmsc_result_unref(result);
    }

// xmmsc_medialib_get_info (conn, ui);
void xmms_playlist_info (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;
    obj->read_Mediainfo(result, true);
    xmmsc_result_unref(result);

    obj->request(Client::REQUEST_PLAYLIST_INFO);
    }

void Client::read_Mediainfo (xmmsc_result_t *result, bool playlist_checks)
    {
    int tmp_int;
    unsigned int id;
    MediaInfo *info;
    bool reloading;
    const char *str;

    if ( xmmsc_result_iserror(result) )
        {
        const char *str = xmmsc_result_get_error(result);
        if ( playlist_checks )
            {
            playlist_info_req = NULL_ID;
            mcp->log_verbose(2, "mediainfo: error (%u): %s\n", id, str);
            for ( unsigned int i = 0; i < playlist_length; i++ )
                if ( playlist[i].id == playlist_info_req )
                    {
                    if ( playlist[i].info )  // hm.. shouldn't happen
                        { playlist[i].info->unref();
                          playlist[i].info = 0; }
                    playlist[i].error = strdup(str);
                    }
            }
        else
            mcp->log_verbose(2, "mediainfo: error: %s\n", str);
        return;
        }

    if ( !xmmsc_result_get_dict_entry_uint(result, "id", &id) )
        {
        if ( !xmmsc_result_get_dict_entry_int(result, "id", &tmp_int) )
            {
            mcp->log_verbose(2, "mediainfo: no id entry?");
            if ( playlist_checks )
                playlist_info_req = NULL_ID;
            return;
            }
        id = (unsigned int)tmp_int;
        }
    if ( playlist_checks )
        assert(id == playlist_info_req);

    info = find_loaded_mediainfo(id);
    reloading = (info != 0);
    if ( !reloading )
        info = new MediaInfo(id);

    if ( xmmsc_result_get_dict_entry_string (result, "artist", &str) )
        info->artist.assign(str);

    if ( xmmsc_result_get_dict_entry_string (result, "album", &str) )
        info->album.assign(str);

    if ( xmmsc_result_get_dict_entry_string (result, "title", &str) )
        info->title.assign(str);

    if ( xmmsc_result_get_dict_entry_string (result, "comment", &str) )
        info->comment.assign(str);

    if ( xmmsc_result_get_dict_entry_string (result, "url", &str) )
        info->url.assign(str);

    if ( xmmsc_result_get_dict_entry_string (result, "picture_front", &str) )
        info->picture_front.assign(str);

// FIXME  I think this was going to be changed at some point..
if ( !xmmsc_result_get_dict_entry_int(result, "duration", &tmp_int) )
        info->duration = 0;
    else
        info->duration = (unsigned int)tmp_int;

    // TODO  short name, description, channels, ...
    
    mcp->log_verbose(5, "mediainfo: info %sread for #%d:\n",
                     reloading ? "re" : "", id);
    mcp->log_verbose(11, "    %s\n", info->title.data());
    mcp->log_verbose(11, "    dur(%u)  artist(%s)\n",
                     info->duration, info->artist.data());
    mcp->log_verbose(11, "    url: %s\n", info->url.data());
    if ( info->picture_front.length() > 0 )
        mcp->log_verbose(11, "    pict: %s\n", info->picture_front.data());

    // Now update the current_info and playlist
    // - could use the reloading flag here, but it is more.. fault tolerant
    //   to just check the actual objects anyway

    if ( current_id == id )
        {
        current_info = info->addref();
        mcp->current_info_updated(current_info);
        }

    bool was_incomplete = false;
    for ( unsigned int i = 0; i < playlist_length; i++ )
        {
        if ( !playlist[i].info && !playlist[i].error )
            was_incomplete = true;
        if ( playlist[i].id == info->id && playlist[i].info != info )
                {
                if ( playlist[i].info )
                    playlist[i].info->unref();
                playlist[i].info = info->addref();
                }
        }
    if ( was_incomplete )
        {
        bool still_incomplete = false;
        for ( unsigned int i = 0; i < playlist_length; i++ )
            if ( !playlist[i].info && !playlist[i].error )
                still_incomplete = true;
        if ( !still_incomplete )
            mcp->playlist_info_updated();
        }

    if ( playlist_checks )
        playlist_info_req = NULL_ID;
    info->addref(); /// free it just in case
    info->unref();  //  it wasn't used
    }

void xmms_changed_mlib (xmmsc_result_t *result, void *userdata)
    {
    Client *obj = (Client *)userdata;
    unsigned int id;
    MediaInfo *info;

    if ( xmmsc_result_iserror(result) || !xmmsc_result_get_uint(result, &id) )
        return;
    xmmsc_result_unref(result);

    if ( (info = obj->find_loaded_mediainfo(id)) == 0 )
        return;

    // FIXME  ideally, add some check for recently requested ids here,
    //        primarily to ignore the current-id-just-changed case
    // if ( info == current_info )
        // .. some kind of useful check

    mcp->log_verbose(10, "x_c_mlib: requesting info for %d\n", info->id);
    result = xmmsc_medialib_get_info(obj->xmms, info->id);
    xmmsc_result_notifier_set(result, xmms_current_info, userdata);
    xmmsc_result_unref(result);
    }

void xmms_picture_front (xmmsc_result_t *result, void *userdata)
    {
    MediaInfo *o = (MediaInfo *)userdata;
    unsigned char *data;
    unsigned int length;

//    o->clear_picture();
    if ( xmmsc_result_get_bin(result, &data, &length) )
        {
        mcp->log_verbose(11, "xmms_picture_front: data for #%u is len %u\n",
                         o->id, length);
        // TODO  keep the result instead of alloc'ing this data again?
//        o->picture_data = (unsigned char *)malloc(o->picture_len);
//        memcpy(o->picture_data, data, o->picture_len);
        // For now, UI objects can copy this if they want to
        mcp->picture_loaded(o->id, data, length, &o->picture_front);
        }
    else
        {
        fprintf(stderr, "xmms_picture_front: no data for %u?\n", o->id);
        mcp->picture_loaded(o->id, 0, 0, 0);
        }

    xmmsc_result_unref(result);
    }

// ---------------------------------------------------------------------- <fold<
//      Client object definition                                          >fold>

const char *Client::userconfdir_get (char *buf, int len)
    {
    return xmmsc_userconfdir_get(buf, len);
    }

Client::Client () : xmms(0)
    {
    // startup values are set by connected()
    playlist = 0;
    current_info = 0;

    mcp->prefs->set_default("launcher", "xmms2-launcher");
    mcp->prefs->set_default("autostart", "yes");
    }

Client::~Client ()
    {
    disconnect();
    }

bool Client::timer_wanted ()
    {
    return need_playlist;
    }

void Client::timer ()
    {
    if ( need_playlist )
        {
        request(REQUEST_PLAYLIST);
        need_playlist = false;
        }
    }

/** (Re)Check the connection status.
 * Returns: True if we are [now] connected to xmms2.
 */
bool Client::connected (bool check_only)
    {
    int n;
//    xmmsc_result_t *result;
    const char *xmms_path;

    if ( xmms ) return true;
    if ( check_only ) return false;

    if ( !(xmms = xmmsc_init("rmc")) )
        {
        fprintf(stderr, "xmmsc_init failure\n");
        return false;
        }

    // TODO more reasonable logging and handling of correctable errors

    n = 0;
    
    if ( (xmms_path = mcp->prefs->get("xmms_path")) )
        {
        n = xmmsc_connect(xmms, xmms_path);
        if ( !n )
            {
#if 0
            if ( KMessageBox.Cancel == KMessageBox::warningContinueCancel(parentWidget(), "Connecting to the configured XMMS_PATH failed.  Continue with the default path?",
                        "rmc-xmms2", "Yes", "noconfirm_default_path", 0) )
            else
#endif
            fprintf(stderr, "xmmsc_connect: failed for specified xmms path \"%s\"\n", xmms_path);
            }
        }

    if ( !n )
        n = xmmsc_connect(xmms, 0);

    if ( !n && mcp->prefs->get("autostart") )
        {
        mcp->fire_event(EVENT_LAUNCHING);
        if ( system(mcp->prefs->get("launcher")) == 0 )
            n = xmmsc_connect(xmms, xmms_path);  // wtf was the next line?
//            n = xmmsc_connect(xmms, xmms_path ? xmms_path : 0);
        // else.. rely on the launcher to write to stderr here
        // TODO message for error trying to fork etc.
        }

    if ( !n )
        {
        mcp->log_verbose(1, "xmms2 connect", xmmsc_get_last_error(xmms));
        disconnect();
        return false;
        }
    else
        {
        current_id = NULL_ID;
        current_index = NULL_ID;
        current_status = 0;
        left_volume = 0;
        right_volume = 0;
        left_muted = false;
        right_muted = false;
        maintain_playtime = false;
        pending_seek = 0;
        need_playlist = 1;
        playlist = 0;
        playlist_info_req = NULL_ID;
        playlist_length = 0;
        current_info = 0;

//        xmmsc_io_need_out_callback_set(xmms, xmms_output_needed, mcp);
        xmmsc_disconnect_callback_set(xmms, xmms_disconnected, this);

        XMMS_CALLBACK_SET(xmms, xmmsc_broadcast_playback_current_id,
                          xmms_changed_id, this);
        XMMS_CALLBACK_SET(xmms, xmmsc_broadcast_playlist_current_pos,
                          xmms_changed_pos, this);
        XMMS_CALLBACK_SET(xmms, xmmsc_broadcast_playback_status,
                          xmms_changed_status, this);
        XMMS_CALLBACK_SET(xmms, xmmsc_broadcast_playback_volume_changed,
                          xmms_changed_volume, this);
        XMMS_CALLBACK_SET(xmms, xmmsc_broadcast_playlist_changed,
                          xmms_changed_playlist, this);

	XMMS_CALLBACK_SET(xmms, xmmsc_broadcast_medialib_entry_changed,
	                  xmms_changed_mlib, this);
        XMMS_CALLBACK_SET(xmms, xmmsc_broadcast_quit,
                          xmms_quit, this);

        // get the initial state of the broadcasts
        request(REQUEST_CURRENT_ID);
        request(REQUEST_INDEX);
        request(REQUEST_STATUS);
        request(REQUEST_VOLUME);
        request(REQUEST_PLAYLIST);

        // TODO possibly wait to send the connected_event until after these
        //      broadcasts have been received so the UI's can assume
        //      useful values for them

        mcp->connected_event();
//        if ( playtime_enabled )
//            enablePlaytime(true, true);

        return true;
        }
    } 

// This only handles objects that need to be freed
// - values are set to their initial values in connect()
void Client::disconnect ()
    {
    if ( xmms )
        {
        xmmsc_unref(xmms);
        xmms = 0;
        }

    if ( playlist )
        {
        for ( unsigned int i = 0; i < playlist_length; i++ )
            if ( playlist[i].info )
                playlist[i].info->unref();
        free(playlist);
        }
    if ( current_info )
        current_info->unref();

    mcp->disconnected_event();
    }

void Client::change_volume (int value, bool absolute_value)
    { change_volume(value, value, absolute_value); }

void Client::change_volume (int left, int right, bool absolute_value)
    {
    xmmsc_result_t *result;

    if ( !absolute_value )
        {
        left = left_volume + left;
        right = right_volume + right;
        // TODO determine if xmms2 handles this range check or if volumes can
        //      have different ranges
        if ( left < 0 )   left  = 0;
        if ( left > 100 ) left  = 100;
        if ( right < 0 )   right = 0;
        if ( right > 100 ) right = 100;
        // kludge, for some reason the daemon is slow about reporting
        // the volume change.  (or something is wrong with this client..)
        // Changing the value of left/right_volume is wrong here, but is done
        // so that quick volume changes will work.
        // The volume change broadcasts should keep the values reliable.
        left_volume = left;
        right_volume = right;
        }

    result = xmmsc_playback_volume_set(xmms, "left", left);
    xmmsc_result_unref(result);
    result = xmmsc_playback_volume_set(xmms, "right", right);
    xmmsc_result_unref(result);
    }

void Client::change_position (unsigned int position_ms)
    {
    xmmsc_result_t *result;
    result = xmmsc_playback_seek_ms(xmms, position_ms);
    // TODO  hm.. is this supposed to be unref'd?
    }

void Client::request_playtime (bool enable, bool force)
    {
    if ( force || maintain_playtime != enable )
        {
        maintain_playtime = enable;
        if ( enable && xmms )
            {
            xmmsc_result_t *result = xmmsc_signal_playback_playtime(xmms);
            xmmsc_result_notifier_set_full(result, xmms_playtime, this, 0);
            xmmsc_result_unref (result);
            }
        }
    }

int Client::descriptor ()
    {
    assert(xmms != 0);
    return xmmsc_io_fd_get(xmms);
    }

bool Client::output_pending ()
    {
    return (xmmsc_io_want_out(xmms) == 1);
    }

void Client::process_output ()
    {
    xmmsc_io_out_handle(xmms);
    }

void Client::process_input ()
    {
    xmmsc_io_in_handle(xmms);
    }

// ---------------------------------------------------------------------- <fold<
//      -   Commands                                                      >fold>

void Client::seek_pending (unsigned int pos)
    {
    pending_seek = pos;
    }

bool Client::command (Command cmd)
    {
    if ( cmd == COMMAND_DISCONNECT )
        {
        disconnect();
        return true;
        }
    else if ( cmd == COMMAND_CONNECT )
        return connected(false);

    if ( !connected(true) )
        return false;

    xmmsc_result_t *result = 0;
    int change = 0;
    switch ( cmd )
        {
        case COMMAND_PLAY:
            if ( !playing() )
                result = xmmsc_playback_start(xmms);
            else
                result = xmmsc_playback_seek_ms(xmms, 0);
            xmmsc_result_wait(result);                                  break;

        case COMMAND_STOP:
            result = xmmsc_playback_stop(xmms);
            xmmsc_result_wait(result);                                  break;

        case COMMAND_PAUSE:
            result = xmmsc_playback_pause(xmms);
            xmmsc_result_wait(result);                                  break;

        case COMMAND_PREVIOUS:
            change = -1;
        case COMMAND_NEXT:
            if ( change != -1 )
                change = 1;
            result = xmmsc_playlist_set_next_rel(xmms, change);
            xmmsc_result_wait(result);
            if ( !xmmsc_result_iserror(result) )
                {
                xmmsc_result_unref(result);
                result = xmmsc_playback_tickle(xmms);
                xmmsc_result_wait(result);
                }                                                       break;

        case COMMAND_FIRST:
            result = xmmsc_playlist_set_next(xmms, 0);
            xmmsc_result_wait(result);
            if ( !xmmsc_result_iserror(result) )
                {
                xmmsc_result_unref(result);
                result = xmmsc_playback_tickle(xmms);
                xmmsc_result_wait(result);
                }                                                       break;

        case COMMAND_LAST:
            unsigned int i;
            if ( get_playlist_length(&i) && i > 0 )
                {
                result = xmmsc_playlist_set_next(xmms, i-1);
                xmmsc_result_wait(result);
                if ( !xmmsc_result_iserror(result) )
                    {
                    xmmsc_result_unref(result);
                    result = xmmsc_playback_tickle(xmms);
                    xmmsc_result_wait(result);
                    }
                }                                                       break;

        case COMMAND_MUTE:
            if ( left_volume > 0 || right_volume > 0 )
                {
                left_muted = left_volume;
                right_muted = right_volume;
                }
            change_volume(0, true);                                     break;

        case COMMAND_UNMUTE:
            if ( left_volume == 0 && right_volume == 0 &&
                 (left_muted != 0 || right_muted != 0) )
                change_volume(left_muted, right_muted, true);           break;

        default:
            mcp->log_verbose(11, "Client: What is command %d?\n", (int)cmd);
            break;
        }

    if ( result )
        {
        if ( xmmsc_result_iserror(result) )
            {
            // TODO  Some errors here should be transmitted back to the UI
            //       (e.g. no volume mixer, next/prev failure)
            mcp->log_verbose(2, "Command #%d failed: %s\n",
                             (int)cmd, xmmsc_result_get_error(result));
            }
        xmmsc_result_unref(result);
        }
    return true;
    }

// ---------------------------------------------------------------------- <fold<
//      -   Requests                                                      >fold>

void Client::request (Request request)
    {
    if ( !xmms )
        return;

    xmmsc_result_t *result = 0;
    switch ( request )
        {
        case REQUEST_CURRENT_ID:
            result = xmmsc_playback_current_id(xmms);                   break;

        case REQUEST_STATUS:
            result = xmmsc_playback_status(xmms);                       break;

        case REQUEST_INDEX:
            result = xmmsc_playlist_current_pos(xmms, 0);               break;

        case REQUEST_VOLUME:
            result = xmmsc_playback_volume_get(xmms);                   break;

        case REQUEST_PLAYTIME:
            result = xmmsc_playback_playtime(xmms);                     break;

        case REQUEST_PLAYLIST:
            result = xmmsc_playlist_list_entries(xmms, 0);              break;

        case REQUEST_CURRENT_INFO:
            if ( current_id != NULL_ID && //!requesting_current_info &&
                 (!current_info || current_info->id != current_id) )
                {
                result = xmmsc_medialib_get_info(xmms, current_id);
                }
            break;

        case REQUEST_PLAYLIST_INFO:
            if ( playlist_info_req != NULL_ID )
                {
                mcp->log_verbose(12, "rpi: still waiting for %u\n",
                                 playlist_info_req);
                return;
                }
            for ( unsigned int i = 0; i < playlist_length; i++ )
                if ( playlist[i].info == 0 && playlist[i].error == 0 )
                    {
                    mcp->log_verbose(10, "rpi: requesting info for [%d].%d\n",
                                     i, playlist[i].id);
                    playlist_info_req = playlist[i].id;
                    result = xmmsc_medialib_get_info(xmms, playlist[i].id);
                    break;
                    }
            if ( result == 0 )
                {
                mcp->log_verbose(11, "rpi: useless playlist info request\n");
                mcp->playlist_info_updated();
                }
            break;

        default:
            mcp->log_verbose(5, "Client: What is request %d?\n", (int)request);
            break;
        }

    if ( result == 0 )  // no request made
        return;

    if ( xmmsc_result_iserror(result) )
        {
        mcp->log_verbose(1, "request #%d error: %s\n",
                         (int)request, xmmsc_result_get_error(result)); 
        // TODO  determine if this still needs to be commented out
        // callback->disconnected();
        }
    else  // this is separate only to avoid repeating that error check
        switch ( request )
            {
            case REQUEST_CURRENT_ID:
                xmmsc_result_notifier_set(result, xmms_got_id, this);
                break;
            case REQUEST_STATUS:
                xmmsc_result_notifier_set(result, xmms_got_status, this);
                break;
            case REQUEST_INDEX:
                xmmsc_result_notifier_set(result, xmms_got_pos, this);
                break;
            case REQUEST_VOLUME:
                xmmsc_result_notifier_set(result, xmms_changed_volume, this);
                break;
            case REQUEST_PLAYTIME:
                xmmsc_result_notifier_set(result, xmms_playtime_single, 0);
                break;
            case REQUEST_PLAYLIST:
                xmmsc_result_notifier_set(result, xmms_read_playlist, this);
                break;
            case REQUEST_CURRENT_INFO:
//                requesting_info_current = true;
//                requesting_id = current_id;
                xmmsc_result_notifier_set(result, xmms_current_info, this);
                break;
            case REQUEST_PLAYLIST_INFO:
//                requesting_info_playlist = true;
//                requesting_id = media_incomplete->id;
                xmmsc_result_notifier_set(result, xmms_playlist_info, this);
                break;
            }

    xmmsc_result_unref(result);
    }

void Client::request_picture (unsigned int id)  //, const char *type)
    {
    xmmsc_result_t *result;
    MediaInfo *o;
    
    if ( !(o = find_loaded_mediainfo(id)) )
        {
        // FIXME  Is this always a bug?
        //        ?  How to handle the case of a non-playlist picture
        mcp->log_verbose(5, "Bug: request_picture called for an id (%u) without info (before update?)\n", id);
        mcp->picture_loaded(id, 0, 0, 0);
        }
    if ( o->picture_front.length() == 0 )
        {
        mcp->log_verbose(10, "reqpic: (%u) has no picture :(\n", id);
        mcp->picture_loaded(id, 0, 0, 0);
        }

#ifdef DEBUG
    mcp->log_verbose(11, "reqpic: (%u) (%s) [%p]\n",
                     id, o->picture_front.data(), o);
#endif
    result = xmmsc_bindata_retrieve(xmms, o->picture_front.data());
    xmmsc_result_notifier_set(result, xmms_picture_front, o);
    xmmsc_result_unref(result);
    }

bool Client::stopped ()
    { return current_status == 0; }

bool Client::playing ()
    { return current_status == 1; }

bool Client::paused ()
    { return current_status == 2; }

bool Client::muted ()
    { return left_muted != 0 || right_muted != 0; }

MediaInfo *Client::get_current_info ()
    {
    if ( xmms )
        return current_info;
    return 0;
    }

bool Client::get_playlist_length (unsigned int *length)
    {
    // FIXME  determine if the playlist is empty vs. unavailable
    //        - make sure the get_playlist_xxx functions do the same
    *length = playlist_length;
    return true;
    }

bool Client::get_playlist_playtime (unsigned int *playtime)
    {
    bool result = true;
    unsigned int len = 0;

    for ( unsigned int i = 0; i < playlist_length; i++ )
        if ( playlist[i].info != 0 )
            len += playlist[i].info->duration;
        else
            result = false;

    *playtime = len;
    return result;
    }

bool Client::get_playlist_playtime_to_current (unsigned int *playtime)
    {
    bool result = true;
    unsigned int len = 0;

    if ( current_index == NULL_ID || !playlist )
        { *playtime = 0; return false; }

    for ( unsigned int i = 0; i < current_index && i < playlist_length; i++ )
        if ( playlist[i].info != 0 )
            len += playlist[i].info->duration;
        else
            result = false;

    *playtime = len;
    return result;
    }

#if 0
bool Client::get_playlist_playtime_after_current (unsigned int *playtime)
    {
    if ( !playlist || current_id == NULL_ID ) return false;

    bool result = false;
    unsigned int len = 0;
    IDInfo *pl;
    for ( pl = playlist; pl && pl->id != current_id; pl = pl->next )
        ;
    if ( !pl )  // hmm... well this shouldn't happen
        {
        fprintf(stderr, "ack!  id(%u) not found in the playlist..\n", current_id);
        return false;
        }
    if ( !pl->next )
        result = true;
    for ( ; pl; pl = pl->next )
        if ( pl->info )
            {
            result = true;
            len += pl->info->duration;
            }
    if ( result )
        *playtime = len;
    return result;
    }
#endif
// --------------------------------------------------------------------<fold< //
